package com.example.gulimall.auth.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.example.common.constant.AuthServerConstant;
import com.example.common.entity.member.MemberEntity;
import com.example.common.exception.Assert;
import com.example.common.exception.RRException;
import com.example.common.utils.HttpUtils;
import com.example.common.utils.R;
import com.example.common.vo.UserLoginVo;
import com.example.common.vo.UserRegisterVo;
import com.example.common.vo.auth.GiteeUserInfoVo;
import com.example.common.vo.auth.OauthGiteeAccessToken;
import com.example.gulimall.auth.feign.MemberFeignService;
import com.example.gulimall.auth.feign.SmsFeignService;
import com.example.gulimall.auth.properties.ThirdLoginProperties;
import com.example.gulimall.auth.service.AuthService;
import com.example.common.utils.GeneratorUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.validation.BindingResult;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.servlet.http.HttpSession;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *
 * </p>
 *
 * @author zyred
 * @since v 0.1
 **/
@Slf4j
@Service
@EnableConfigurationProperties(ThirdLoginProperties.class)
public class AuthServiceImpl implements AuthService {

    private static final String REGEX = "_";
    private static final int INTERVAL_BETWEEN = 60 * 1000;

    @Autowired private RestTemplate restTemplate;
    @Autowired private SmsFeignService smsFeignService;
    @Autowired private StringRedisTemplate redisTemplate;
    @Autowired private MemberFeignService memberFeignService;
    @Autowired private ThirdLoginProperties thirdLoginProperties;
    @Autowired private MapperFactory mapperFactory;;
    @Autowired private MapperFacade mapperFacade;

    @Override
    public R sendSms(String phone) {
        String code = GeneratorUtils.code_6();
        String key = AuthServerConstant.SMS_CODE_CACHE_PREFIX.concat(phone);

        String getValue = this.redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(getValue)) {
            long lastTime = Long.parseLong(getValue.split(REGEX)[1]);
            if ((System.currentTimeMillis() - lastTime) < INTERVAL_BETWEEN) {
                throw new RRException("验证码获取频率太快!");
            }
        }

        String val = code.concat(REGEX).concat(String.valueOf(System.currentTimeMillis()));
        // 缓存验证码
        this.redisTemplate.opsForValue().set(key, val, 5, TimeUnit.MINUTES);
        return this.smsFeignService.sendSms(phone, code);
    }

    @Override
    public String register(UserRegisterVo vo, BindingResult result, RedirectAttributes attributes) {

        //1.判断校验是否通过
        Map<String, String> errors = new HashMap<>();
        if (result.hasErrors()) {
            //1.1 如果校验不通过，则封装校验结果
            result.getFieldErrors().forEach(item -> {
                // 获取错误的属性名和错误信息
                errors.put(item.getField(), item.getDefaultMessage());
                //1.2 将错误信息封装到session中
                attributes.addFlashAttribute("errors", errors);
            });
            //1.2 重定向到注册页
            return "redirect:http://auth.gulimall.com/reg.html";
        }

        //判断验证码是否正确
        String code = redisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX.concat(vo.getPhone()));
        //2.1 如果对应手机的验证码不为空且与提交的相等-》验证码正确
        if (!StringUtils.isEmpty(code) && vo.getCode().equals(code.split("_")[0])) {
            //2.1.1 使得验证后的验证码失效
            redisTemplate.delete(AuthServerConstant.SMS_CODE_CACHE_PREFIX + vo.getPhone());

            //2.1.2 远程调用会员服务注册
            R r = memberFeignService.register(vo);
            if (r.getCode() == 0) {
                //调用成功，重定向登录页
                return "redirect:http://auth.gulimall.com/login.html";
            }

            //调用失败，返回注册页并显示错误信息
            String msg = (String) r.get("msg");
            errors.put("msg", msg);
            attributes.addFlashAttribute("errors", errors);
            return "redirect:http://auth.gulimall.com/reg.html";
        }
        //2.2 验证码错误
        errors.put("code", "验证码错误");
        attributes.addFlashAttribute("errors", errors);
        return "redirect:http://auth.gulimall.com/reg.html";
    }

    @Override
    public String login(UserLoginVo vo, RedirectAttributes attributes) {
        R r = this.memberFeignService.login(vo);
        Object data = r.get("data");
        log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        log.info("远程会员服务返回结果：{}", r.get("msg"));
        log.info("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");

        if(Objects.nonNull(data)){
            attributes.addFlashAttribute("data", r.get("data"));
            return "redirect:http://gulimall.com";
        }
        attributes.addFlashAttribute("errors", r.get("msg"));
        return "redirect:http://auth.gulimall.com/login.html";
    }

    @Override
    @SneakyThrows
    public String giteeSussess(String code, HttpSession session) {
        // 1. 获取到的code再去换取accessToken
        if (StringUtils.isBlank(code)) {
            log.error(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
            log.error("gitee 请求授权码失败：{}", code);
            log.error("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
            return "redirect:http://auth.gulimall.com/login.html";
        }

        //1、根据用户授权返回的code换取access_token
        HttpResponse response = HttpUtils.doPost(
                this.thirdLoginProperties.getUri(),
                this.thirdLoginProperties.getPath(),
                Collections.emptyMap(),
                this.thirdLoginProperties.getParam(code),
                Collections.emptyMap()
        );

        log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        log.info("gitee 授权登录返回状态码：{}", response.getStatusLine().getStatusCode());
        log.info("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");

        if (response.getStatusLine().getStatusCode() == HttpStatus.OK.value()) {

            OauthGiteeAccessToken entityBody = JSONObject.parseObject(EntityUtils.toString(response.getEntity()), OauthGiteeAccessToken.class);
            if (Objects.nonNull(entityBody)) {
                log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
                log.info("gitee 授权登录返回 accessToken：{}", entityBody.getAccessToken());
                log.info("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");

                // 2. 查询当前用户是否存在
                MemberEntity userInfo = this.getUserInfo(entityBody.getAccessToken());
                Assert.isBlank(userInfo, "accessToken获取失败，无法从gitee获取用户信息!");
                MemberEntity m = this.memberFeignService.isRegister (userInfo);

                // 3. 登录成功后，将 token 信息保存到 redis 中
                String key = AuthServerConstant.THIRD_TOKEN_PREFIX_GITEE.concat(String.valueOf(userInfo.getThirdId()));
                this.redisTemplate.opsForValue().set(key, entityBody.toString(), entityBody.getExpiresIn(), TimeUnit.SECONDS);

                // 4. 将用户信息放入 session 中
                session.setAttribute(AuthServerConstant.SESSION_LOGIN_KEY, m);
                return "redirect:http://gulimall.com";
            }
            log.error(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
            log.error("gitee 授权登录返回为null");
            log.error("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
            return "redirect:http://auth.gulimall.com/login.html";
        }

        return "redirect:http://auth.gulimall.com/login.html";
    }

    @Override
    public String logout(HttpSession session) {
        MemberEntity memberEntity = (MemberEntity)session.getAttribute(AuthServerConstant.SESSION_LOGIN_KEY);
        if (Objects.nonNull(memberEntity)) {
            String key = AuthServerConstant.THIRD_TOKEN_PREFIX_GITEE.concat(String.valueOf(memberEntity.getThirdId()));
            this.redisTemplate.delete(key);
        }
        session.removeAttribute(AuthServerConstant.SESSION_LOGIN_KEY);
        return "redirect:http://auth.gulimall.com/login.html";
    }


    /**
     * 从 gitee 获取用户信息
     * @param accessToken   从 gitee 获取的 令牌
     * @return              自己的实体类
     */
    private MemberEntity getUserInfo (String accessToken) {
        if (StringUtils.isBlank(accessToken)) {
            log.error(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
            log.error("获取用户信息失败，原因是获取 accessToken 为：{}", accessToken);
            log.error("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
            return null;
        }

        String url = this.thirdLoginProperties.getUserInfos().concat(accessToken);
        GiteeUserInfoVo template = this.restTemplate.getForObject(url, GiteeUserInfoVo.class);

        this.mapperFactory.classMap(GiteeUserInfoVo.class, MemberEntity.class)
                .field("name", "username")
                .field("name", "nickname")
                .field("avatarUrl", "header")
                .field("id", "thirdId")
                .byDefault()
                .register();
        return this.mapperFacade.map(template, MemberEntity.class).getDefault();
    }
}
